Technical Note TN2078
Migrating to FSRefs & long Unicode names from FSSpecs

目次

FSRefs への移行

[2003 年 5 月 6 日]

 

 

 

 

 

File Manager コードをベースとした従来の FSSpec から、API をベースとした FSRef への移行を検討すべき理由として、Unicode のロングファイル名のサポート、大型ファイルへのアクセス、優れたパフォーマンスなどがあります。

移行する前に、ファイルパス、Unicode、相互運用性について知っておくべきことがいくつかあります。

この記事の初めの 2 つのセクションでは、移行中に直面する問題の多くを取り上げ、サンプルコードと、与えられる選択肢についての詳細を提供します。また、移行作業を簡潔にするためのヒントと秘訣も紹介します。最後のセクションでは、Unicode について深く掘り下げます。


FSSpec と FSRef

ここでは、FSSpec から FSRef へ、ソースを移行する際に役立つ情報とコーディングテクニックについて説明します。

FSSpec と FSRef との相違点

リスト 1. 「Files.h」で定義される構造体

struct FSSpec {
  short         vRefNum;
  long          parID;
  StrFileName   name;          /* 63ビット文字列 */
};

struct FSRef {
  UInt8         hidden[80];    /* File Manager のプライベート変数 */
};

おそらく、コードに最も大きな影響を与える相違点としては、FSRef は存在しない項目を表現できないことと、80 バイトの配列として定義されていますが、その内容が明文化されていない不透過なデータ構造体であるということです。特に FSRef は、それが参照している項目の名前を保持していません。Mac OS X が最大 255 文字の UniChar からなる、Unicode 文字のファイル名を使用できることを考えれば、不思議なことではありません(詳しくは「FSRef と Unicode のロングファイル名」を参照してください)。

 

FSSpec から FSRef への変換と戻し方

FSSpec から FSRef へ変換するには、
err = FSpMakeFSRef( &fsSpec, &fsRef );

FSRef から FSSpec を取得するには、
err = FSGetCatalogInfo( &fsRef, kFSCatInfoNone, NULL, NULL, &fsSpec, NULL );

FSRef が有効かどうかを調べるには

Boolean FSRefIsValid( const FSRef &fsRef )
{
・・return ( FSGetCatalogInfo( &fsRef, kFSCatInfoNone, NULL, NULL, NULL, NULL ) == noErr );
}

2 つの FSRef が同じオブジェクトを表しているかどうかを調べる方法

if ( FSCompareFSRefs( &fsRef1, &fsRef2 ) == noErr )

FSRef の親ディレクトリの取得

err = FSGetCatalogInfo( &fsRef, kFSCatInfoNone, NULL, NULL, NULL, &parentFSRef );

これから作成するファイルなど、存在しない項目を指定するには

リスト 2. 多くのデベロッパによって採用されている一般的な方法は、構造体またはクラスのような擬似 FSSpec を作成することです

struct ExtFSRef {
  FSRef           parentFSRef;
  HFSUniStr255    name;
};

class CExtFSRef {
  FSRef           parentFSRef;
  HFSUniStr255    name;
  // ...いくつかの有用なメンバ関数
};

このテクニックは、NavCreatePutFileDialog() によって返されるデータを保存する場合に特に役立ちます。Unicode 対応のファイル作成 API は、親 FSRef と、HFSUniStr255 名を受け取るため、これらの情報を CFURLRef に変換してから親 FSRef と HFSUniStr255 に変換するのではなく、そのまま直接保存しています。NavReplyRecord に保存するときには CFStringRef として保存されるので、名前を CFStringRef として保存し、その後必要に応じて HFSUniStr255 を作成することももちろん可能です。

リスト 3. HFSUniStr255 ではなく CFStringRef としてファイル名を保存

struct ExtFSRef2 {
  FSRef            parentFSRef;
  CFStringRef      name;
};

class CExtFSRef2 {
  FSRef            parentFSRef;
  CFStringRef      name;
  // ...いくつかの有用なメンバ関数
};

AppleEvent

AppleEvent で FSRef を渡さないでください。Mac OS X では、プロセスをまたいで FSRef が 有効であるということは保証されていないため、AppleEvent に FSRef を送るべきではありません。MoreFinderEvents には、アップルイベントを通じて Finder にエイリアスを送る方法を実演するコードが記載されています。

 

永続ストレージ

FSSpec と同様、FSRef は、Mac OS 9 と Mac OS X では再起動した場合、Mac OS X では複数のプロセスをまたぐ場合、さらには Mac OS X では同じアプリケーションを別々に起動している場合に有効であるという保証がないため、永続ストレージを必要とする場合は FSRef を使用しないでください。永続ストレージについては、やはりエイリアスの使用をお勧めします(Alias Manager 参照)。

 

FSSpec を使い続けることはできますか?

できます。FSSpec は有効なファイル参照の方法であり続けます。ただし、FSSpec で取得した名前はマングルされる可能性があるので、FSSpec を使って保存または表示の目的でファイル名を取得するのは避けてください。ファイル名は、実際のファイル名を Pascal 文字列に保存できない場合や、31 ビットより長い名前の場合にマングルされます。後者の場合は、「A really, really long file#23A4」といった名前が返されます。これは、項目の実際の名前を含んでいないだけで、FSSpec 自体は動作しています。

 

FSSepc への参照をすべて FSRef に置き換えることはできますか?

アプリケーションによって異なります。
QuickTime と Drag Manager の API は現在も FSSpec を必要としますが、簡単な処理で FSRef から一時的な FSSpec を作成できます。

 

「開く」および「保存」のダイアログで FSRef と Unicode のロングファイル名をサポートには

Navigation Services 3.0 で導入された新しい NavCreateXXX API を使う必要があります(Navigation Services 参照)。また、「開く」および「保存」のダイアログをシートとして実装する場合も(シートである必要はありませんが)、NavCreateXXX API を使う必要があります。

新しい NavCreateXXX API のいずれかに対して kWindowModalityAppModal を使うと、ユーザがダイアログを閉じるまで NavDialogRun() は戻りません。kWindowModalityWindowModal を使ってシートの動作を実装すると、Mac OS X では NavDialogRun() はすぐに戻ってきます。つまり、NavDialogRun は処理を続けていますが、シートは通常のウインドウのように振る舞うということです。この振る舞いは、アプリケーションモーダルからウインドウモーダル(シート)に移行するときに見落とされがちです。

 

 

アプリケーションへの FSRef を取得する方法

リスト 4. 単一の CFM バイナリの場合は、FSSpec を取得してそれを FSRef に変換できます

OSErr GetCurrentProcessFSSpec( FSSpec *outFSSpec )
{
  ProcessSerialNumber  currentProcess = { 0, kCurrentProcess };
  ProcessInfoRec       processInfo;
  processInfo.processInfoLength = sizeof(ProcessInfoRec);
  processInfo.processName       = NULL;  /* プロセス名を必要としない */
  processInfo.processAppSpec    = outFSSpec;
  return GetProcessInformation( &currentProcess, &processInfo );
}

アプリケーションがバンドルされている場合、リスト 5 のコードは、バンドルフォルダではなく実行可能ファイルの FSRef を取得します。

リスト 5. アプリケーションのバンドル、CFM または Mach-O、に対する FSRef を取得するには、以下のコードを使用します

OSErr GetMyBundleFSRef( FSRef *outFSRef ) 
{
  ProcessSerialNumber  currentProcess = { 0, kCurrentProcess }; 
  return( GetProcessBundleLocation( ¤tProcess, outFSRef ) ); 
}      


LaunchServices

LaunchServices は、Mac OS X 専用の、ファイルを処理するための一連の API です。Mac OS X でのファイルに関する最新情報を知りたい場合は、 をお読みください。バンドルされたアプリケーション、表示名、アプリケーションのバインディングに関する新しい規則など、いくつかの新しい問題が記述されています。テクニカルノート 2017「Using Launch Services for discovering document binding and launching applications」 にも、豊富な情報が提供されています。

リスト 6. パッケージは、ファイルとフォルダの中間のようなものです。Mac OS X では、LSCopyItemInfoForRef() を使って、フォルダがパッケージ/バンドルされたアプリケーションであるかどうかを調べられます

OSStatus LSIsApplication( const FSRef *inRef, Boolean *outIsApplication,
                          Boolean *outIsBundled )
{
  LSItemInfoRecord  info;
  OSStatus  err = LSCopyItemInfoForRef( inRef, kLSRequestBasicFlagsOnly,
                                        &info );
  
  if ( err == noErr )
  {
    *outIsApplication = ( kLSItemInfoIsApplication &info.flags ) != 0;
    *outIsBundled = ( kLSItemInfoIsPackage &info.flags ) != 0;
  }
  return( err );
}

バンドルされたアプリケーションのタイプとクリエータを取得するには、kLSRequestTypeCreator マスクとともに LSCopyItemInfoForRef() を使います。FSGetCatalogInfo() は、バンドルされたアプリケーションを通常のフォルダとして扱うため、バンドルされたアプリケーションのファイルタイプやクリエータにアクセスする目的には使用できません。
ファイルを開く際に Finder がどのアプリケーションを使用するかを調べるには、LSGetApplicationForItem() を使います。
特定の拡張子、タイプ、またはクリエータのファイルを開く際に使われるアプリケーションの場所を調べるには、LSGetApplicationForInfo() を使います。OS X では、アプリケーションとファイルをバインディングするための複雑な規則があることと、特定のクリエータ、タイプ、拡張子を持つすべてのファイルを開くためのアプリケーションをユーザ側で指定でき、予想されるデフォルトの動作を無効にできることから、このような機能が必要になっています。
Finder に表示される名前を取得するには、LSCopyDisplayNameForRef() を使います。たとえば、Finder の環境設定で「常にファイル拡張子を表示する」オプションが選択されていない場合、LSCopyDisplayNameForRef() によって返される名前は、Finder に拡張子が表示されていなければ拡張子を含みません。たとえば、TextEdit のフルネームは「TextEdit.cpp」ですが、LSCopyDisplayNameForRef() は「TextEdit」を返します。すべての Launch Services API と同様、Mac OS 9 では LSCopyDisplayNameForRef() は利用できませんが、Mac OS 9 の Finder は常にファイルシステム名を表示するため、問題ありません。

 

ファイルパスの取得

ファイルパスを取得する最も簡単な方法は、次の API を使用することです。
OSStatus FSRefMakePath( const FSRef * ref, UInt8 * path, UInt32 maxPathSize);
この API は、FSRef によって指定されたオブジェクトに、UTF8 によってエンコードされたパスを返します。このルーチンを使う上での短所は、固定サイズのバッファに渡す必要があることです。このため返されるパスがバッファサイズに納まらないと、エラーが返されます。この API の「Files.h」のコメントを読むと、Mac OS 9 ではマングルされた名前が返される場合があることがわかります。「Files.h」にはまた、FSRefMakePath() が HFS のファイルパス (Mac OS 9) を返す場合と、UTF-8 形式 (Mac OS X) の POSIX パスを返す場合についても記述されています。


リスト 7. ファイルのパスを取得するもう 1 つの方法は、CFURLRef を作成し、これを使ってファイルのパスを取得することです

CFURLRef url = CFURLCreateFromFSRef( kCFAllocatorDefault, &fsRef );
CFStringRef cfString = NULL;
if ( url != NULL )
{
  cfString = CFURLCopyFileSystemPath( url, inPathStyle );
  CFRelease( url );
}

リスト 8. Mac OS X で HFS 形式のパスを取得したい場合、または FSRefMakePath() がうまくいかない場合は、下記に示すようなコードを使ってパスを作成できます

Boolean GetPathManually( const FSRef *inFSRef, CFMutableStringRef ioPath,
                         UniChar inSepChar )
{
  // ioPath は CFStringCreateMutablexxx で既に作成済み
  FSCatalogInfo catalogInfo;
  int           n;
  int           i;
  HFSUniStr255  names[100];
  FSRef         localRef  = *inFSRef;
  OSStatus      err       = noErr;

  CFStringDelete( ioPath, CFRangeMake( 0, CFStringGetLength( ioPath ) ) );
  for ( n=0 ; err==noErr && catalogInfo.nodeID != fsRtDirID && n<100 ; n++ )
  {
    err = FSGetCatalogInfo( &localRef, kFSCatInfoNodeID, &catalogInfo,
                           &names[n], nil, &localRef );
  }
  for ( i = n - 1; i >= 0; --i )
  {
    CFStringAppendCharacters( ioPath, names[i].unicode, names[i].length );
    if ( i > 0 )
      CFStringAppendCharacters( ioPath, &inSepChar, 1 );
  }
  return( err == noErr );
}

 

その他の注意事項

FSRef の内容については明文化されていないため、FSRef が参照している項目を含んでいるボリュームの形式によって異なる可能性があります。現在のところ、HFS または HFS+ のボリューム上の項目の FSRef は、おそらく項目のファイル ID またはディレクトリ ID とボリューム参照番号を含んでいるため、項目が移動したり名前が変更されたりしても、有効な状態が続きます。この点で、FSRef は FSSpec よりも堅牢であるといえます。ただしこれは現在の状態であり、他の不透過なデータ構造体と同様に、いずれも変更される可能性があるので、これに依存するのは避けてください。堅牢なファイルトラッキングが必要な場合は、エイリアスを使用してください(Alias Manager 参照)。

CarbonLib

CarbonLib プロジェクトの使用を検討している場合は、FSRef が Mac OS 9 で新しい HFS+ API とともに導入されているため、Mac OS 9 以降が必要であることに注意してください。CarbonLib は FSRef API のラッバを提供していますが、実際の API を実装しているわけではないため、Mac OS 8 ではどのバージョンも、CarbonLib を使って FSRef の機能を実現することはできません。

 

先頭に戻る



FSRef と Unicode のロングファイル名

 

FSRef から項目名を取得する方法

リスト 9. Unicode 名の取得

OSErr FSRefGetName( const FSRef *fsRef, HFSUniStr255 *name )
{
  return( FSGetCatalogInfo(fsRef, kFSCatInfoNone, NULL, name, NULL, NULL) );
}

An HFSUniStr255 is defined as:

struct HFSUniStr255 {
  UInt16 length;        /* Unicode 文字の数 */
  UniChar unicode[255]; /* Unicode 文字 */
};

HFSUniStr255 は 512 バイトの容量を占めるので、次のように CFStringRef として名前を保存してもよいでしょう。
strRef = CFStringCreateWithCharacters( kCFAllocatorDefault, name.unicode, name.length );

メモリの節約のほか、Core Foundation は CFString のテストと操作を実施する数多くの API を提供しています。HFSUniStr255 のファイル名を処理する API はありません。そのようなテストと操作は、HFSUniStr255 から取得した CFStringRef または CFMutableStringRef を使って行うものと想定されています。

FSGetCatalogInfo() はファイルシステム名を返します。Mac OS X の Finder は、ファイル名の拡張子を常に表示するわけではありません。Finder が表示する名前は表示名と呼ばれます。 Mac OS X で表示名を必要とする場合は、「LaunchServices」のセクションを参照してください。

注:HFSUniStr255 の定義は、技術的には正解ですが若干誤解を招く可能性があります。HFS+ ディスクは、アップルによって修正された Normalization Form D(分解済み)という形式の UTF-16 としてファイルを保存します。これは、単一の Unicode コードポイント値(code point value)が HFSUniStr255 において UniChar の 2 文字分以上を占めることがあることを意味します。これはつまり、ファイル名が見た目では 255 文字以下に制限されることを意味します(Unicode 文字の用語については「Unicode 用語」を参照してください)。

Unicode 文字列についての注意

厳密には、ここで扱う問題は CFString のソースとは無関係ですが、Unicode ファイル名を扱うときにしばしば直面することです。

多くの人が、アプリケーションでファイルやフォルダの名前を表示することを必要としています。Mac OS X は Unicode のロングファイル名をサポートしているため、それに関連する問題がいくつか存在します。Unicode とその仕組みに慣れていないと、予想していなかった処理が背後で行われていることがあります。以下に、Unicode のファイル名を扱うときに覚えておくべき基本事項をいくつか示します。

Unicode 文字列は(Mac OS X の観点からいうと)UniChar の文字列です。こうした文字列は、CFStringRef や CFMutableStringRef との間で相互変換ができます。

単一の Unicode コードポイントは複数の UniChar を要求する場合があるため、単に UniChars の範囲を削除したり、任意のオフセットに UniChar を挿入することによって Unicode 文字列を変更してはなりません。そのような操作をすると、期待していた文字列ではなく、誤った文字列を作り出したり、正当な Unicode 文字列ではない文字列を残したりすることさえあります。

 

幅を基準とした Unicode 文字列の切り詰め

 

リスト 10. 描画またはそのほかの用途に使用する前にファイル名を任意の幅に切り詰めるには、ファイル名を CFMutableStringRef に変換し、TruncateThemeText() を使って切り詰めます

Boolean TruncateWidth( CFMutableStringRef ioString, SInt16 inMaxWidth,
                       TruncCode inTruncCode, ThemeFontID inThemeFontID,
                       ThemeDrawState inState )
{
  Boolean  wasTruncated = false;
  OSStatus err = TruncateThemeText( ioString, inThemeFontID, inState,
          inMaxWidth, inTruncCode, &wasTruncated );
  return( (err == noErr) && wasTruncated );
}

 

長さを基準をとした Unicode 文字列の切り詰め

残念ながら、Unicode 文字列を特定の文字数に正確に切り詰めてくれる簡単な API はありません。長さを基準としてファイル名を切り詰めるには、ファイル名を UniChar 文字列に変換してから、kUCTextBreakClusterMask とともに UCFindTextBreak() を使う必要があります(実際は多少コードを必要としますが、ここでは説明を簡略化します)。UCFindTextBreak() を使用すると、クラスタ、すなわちの意味を持つ UniChar の最小グループの途中で文字列を切り詰めないことが保証されます。クラスタから 1 文字以上を削除すると、よくても意味が変わり、最悪の場合はもはや正当な Unicode ではなくなってしまいます(「Unicode 用語」の「書記素 (grapheme) クラスタ」を参照してください)。

 

連結

Unicode 文字列は任意に連結できます。個々のかたまりは、元の意味を失いません。たとえば、Unicode 文字列に「.txt」を付加しても、既存の文字列の意味は変わりません。または、英語とアラビア語(右から左へ記述するスクリプト)を連結して期待する結果を得ることができます。

文字列の幅の決定

Unicode 文字列の幅を、その文字列に含まれる UniChar の数を基準に判断しようとしないでください。合成文字とサロゲートのペアの問題に加え、Unicode テキストはレンダリングされない不可視の文字を含んでいることがあります。Unicode には、単なる文字とスクリプトのエンコーディングという範囲を超えた部分があるのです。Unicode のコードポイントの中には、それ自体はレンダリングされないけれどもレンダリングソフトウェアにヒントや指示を提示するのに使われるものがあります。

リスト 11. CFString として保存されている Unicode 文字列の幅を調べるには、次のコードを使用します

SInt16 GetWidth( const CFStringRef inString, ThemeFontID inFontID,
                 ThemeDrawState inState )
{
  Point  pt;
  SInt16 baseline;
  GetThemeTextDimensions( inString, inFontID, inState, false, &pt,
                          &baseline );
  return( pt.h );
}

 

ほかの形式でのファイル名エンコーディング

ここでもまた、ファイル名に限った問題ではありませんが、CFString を C 文字列に変換する際に必要なバッファサイズを CFStringGetSize( cfString ) と想定する間違いがよくあるので取り上げます。なぜなら、単一の UniChar(および単純な Latin 文字を除くすべて)は、kCFStringEncodingUTF8 を使ってエンコードされるときに複数の文字を必要とするためです。

リスト 12. 必要なバッファサイズを決定する正しい方法は、次のように CFStringGetMaximumSizeForEncoding() を使うことです

char* CreateUTF8CStringFromCFString( const CFStringRef inString )
{
  // 成功して結果が得られたら DisposePtr(cStr) を呼び出す必要がある
  CFIndex  max;
  char     *cStr;
  CFIndex  len   = CFStringGetLength( inString );
  
  max = CFStringGetMaximumSizeForEncoding( len, kCFStringEncodingUTF8 );
  cStr = NewPtr( 1 + max );
  if ( cStr != NULL )
  {
    if ( !CFStringGetCString(inString, cStr, len, kCFStringEncodingUTF8) )
    {
      DisposePtr( cStr );
      cStr = NULL;
    }
    if ( cStr != NULL )
      SetPtrSize( cStr, strlen( cStr ) + 1 );
      // 新しいサイズが古いサイズより小さい場合、SetPtrSize は、
      // Mac OS X では作用しない。実際には、ポインタを本
      // 来使用する容量よりも小さくしたいのであれば、Mac OS X では、
      // サイズが strlen(cStr) + 1 の新しいポインタを割り当て、
      // cStr の内容を新しいポインタにコピーする必要がある
  }
  return( cStr );
}

 

ファイル名がエンコードされる方法

HFS+ ディスクは、アップルによって修正された Normalization Form D(分解済み)という形式の UTF-16 としてファイルを保存します。この形式は、互換性を保つための一定の分解とシンボルブロックの一部を排除して、Mac OS エンコーディングのファイル名との相互変換を保証しています(HFS API を使用するアプリケーションは、入力されたバイト列と出力されたバイト列が同一であると想定しています)。

Mac OS X 10.2 では、(中間時点の草案に基づく)Unicode 2.0.x および上記のアップルによる修正から、Unicode 3.2 および上述のアップルによる修正へと、使用される分解規則が変更されました。Unicode Consortium は、Unicode 3.2 以後は分解規則の変更を行わないと明言しているため、我々も今後はこのような変更は行わないでしょう。2.0.x から 3.2 への変更は、A) 新しい分解が数多く追加されていた、B) 2.0.x のデータはエラーに満ちていた、という理由で必要な措置だったのです。

他のファイルシステムは、異なる保存形式を使用しています。UFS ディスクは UTF-8 を、HFS ディスクは Mac OS エンコーディングを使用しています。AFP (AppleShare) は、3.0 以前は Mac OS エンコーディングを、3.0 以降は UTF-16 を使用しています。


先頭に戻る

Unicode の使用に関するメモ

このセクションは、「ファイル名のための Unicode」としてもよいかもしれません。Unicode には、ここでは取り上げないさまざまな側面がありますが、それらは、Mac OS X での Unicode の処理が、ファイル名の扱いに限られている場合は必要ではないため、ここでは取り上げませんでした。この特定の領域に焦点を当てた理由は、これが、すべての Mac OS X アプリケーションがサポートすべき領域であるためです。Unicode 対応のワードプロセッサを書いている場合は、ここで扱う用語説明よりもさらに多くのことを理解する必要があります。ここで取り上げる内容の大部分は、Richard Gillam 著『Unicode Demystified』からの引用です。しかし、この本は 800 ページもあり、Mac OS X でファイル名を適切に処理したいだけであれば、ボリュームが多すぎます。

Unicode とは?

Unicode は、コンピュータでの使用と保存に適した形式で文字を表現するための、汎用テキストエンコーディング標準です。Unicode の目標は、現在世界中で使われている全部の文字または主要な大半の文字のほか、今は使われていないけれども歴史的または学術的な意義を持つ文字の多くのエンコーディングを可能にすることです。

Unicode 用語

Unicode を初めて扱う方にとっては、2 つの大きな課題があります。1 つは用語についての理解です。もう 1 つも用語と関連しますが、通常の文字と Unicode での文字がどのように構成されているかと、この 2 つがどのように関係しているか(たとえば、文字が Unicode ではどのようにエンコードされるか)を理解することです。英語は、コンピュータでの利用のために記述し、エンコードする上で、世界中の言語のうち最もシンプルな言語(の 1 つ)です。当然、英語を母国語とする人々は、英語以外の言語がどのように記述され、どのように Unicode にエンコードされるかについて間違った推測をしがちです。間違った推測に基づいてコードが記述されると、そのコードはどの言語についても正しく動作しません。以下に、Unicode の解説で頻繁に使われる用語をいくつか挙げます。

文字は、「ラテン文字の A」や、「'sun' を表す漢字」など、言語上の抽象概念のことです。

Unicode 標準で定義される文字にはすべて、21 ビットからなる単一の抽象コードポイント値が割り当てられます。アップルでは、Unicode のコードポイント値を Unicode スカラー値と呼んでいます。

MacTypes.h について説明すべき用語は以下の通りです。
typedef UInt32 UnicodeScalarValue
typedef UInt16 UniChar
typedef UInt16 UTF16Char
UniChar デフォルトの UTF-16 形式の、16 ビット Unicode コードポイント値

UTF16Char

UnicodeScalarValue の 0-0xFFFF は、同じ値を持つ単一の UTF16Char を使って UTF-16 形式で表現されます。UnicodeScalarValue の 0x10000-0x10FFFF は、1 組の UTF16Char を使って表現されます。つまり、高位サロゲート値域 (0xD800-0xDBFF) とそれに続く低位サロゲート値域 (0xDC00-0xDFFF) になります。3.0 までの Unicode のバージョンで定義される文字はすべて、0-0xFFFF の範囲にあり、単一の UTF16Char を使って表現できます。したがって、「Unicode 文字」という用語は、一般的に UniChar すなわち UTF16Char を指します。


Unicode 用語では、基本多言語面 (Basic Multilingual Plane) または BMP は、U+0000 から U+FFFF のコードポイント値を指し、元々の Unicode のエンコーディング空間でした。その後、さらなる空間が必要であることがわかり、16 の補足面(supplementary plane)がエンコーディング空間に追加され、コードポイント値は 16 ビットから 21 ビットに拡張されました。このため BMP には、単に上位 5 つのゼロビットを削ることによって、対応する UniChar に変換できるコードポイント値が含まれています。n 番目の補足面は U+n0000 から U+nFFFF の範囲のコードポイント値を含み、n は 0x01 から 0x10 の範囲をとります。したがって、Unicode コードポイント値の完全な範囲は、0x0000 から 0x10FFFF ということになります。面 3 から 13(U+30000 〜 U+EFFFF)は現在使用されておらず、将来の使用のために確保されています。

サロゲートペア―Unicode では、BMP の 2,048 のコードポイント値(U+8000 〜 U+DFFF)は、実際の文字には割り当てられません。これらは、BMP の範囲外の文字を表現するときに使う、組み合わせ文字を定義するために確保されています。このような値をサロゲートと呼んでいます。最初の 1,024 のサロゲート値(U+D800 〜 U+DBFF)を上位サロゲート、残りの 1,024 のサロゲート値(U+DC00 〜 U+DFFF)を下位サロゲートと呼びます。補足面の文字(BMP にない文字)は、上位サロゲートとそれに続く下位サロゲートによって表現されます。サロゲートは上位と下位がペアになっている場合にのみ有効となります。ペアになっていないサロゲートは、Unicode ではエラーと見なされます。

21 ビットコードポイント値がサロゲートペアにマップされる仕組みをどうしても知りたいという場合は、次のように考えてください。まず、元のコードポイント値から 0x10000 を引いて、20 ビットの値にします。この 20 ビットを 2 等分して、2 つの 10 ビットシーケンスにします。最初の 10 ビットシーケンスが上位サロゲート値の下位 10 ビットになり、2 つ目の 10 ビットシーケンスが下位サロゲート値の下位 10 ビットになります。

結合記号 (combining marks)は、それ自体は文字を表現するものではなく、その前に置かれている基本文字にマークを付けるコードポイント値です。発音区別記号は、結合記号の一種です。たとえば、

é = e + ´ (U+0065 LATIN SMALL LETTER E) + (U+0301 COMBINING ACUTE ACCENT)

書記素 (grapheme)は、書記言語の最小の書記単位です。つまり、ある特定の書記言語の平均的な読み手や書き手によって 1 つの「文字」と見なされる記号のことです。

書記素クラスタは、1 つ以上の Unicode コードポイント (UniChar) の並びであり、検索と並べ替え、ヒットテスト、矢印キーの動作など、Unicode テキストを操作するほとんどの処理において、不可分の単位として扱われます。ドキュメント中や、kUCTextBreakClusterMask のようなヘッダで使われている「クラスタ」という用語は、書記素クラスタのことを指しています。

グリフは、文字を具体的に視覚的に表現したものです。画面や印刷物に表示されているものがそうです。

 

切り詰めとほかの操作

Unicode の元々の目的は、すべての文字を単一の UniChar で表現することでしたが、すぐにそれは不可能であることが判明しました。現在、95,000 文字以上が Unicode 標準で定義され、単一の 16 ビット値で表現できる範囲をはるかに超えています。基本多言語面のコードポイント値だけは、単一の UniChar で表現できます。さらに、多数の文字が基本文字と 1 つ以上の発音区別記号または結合記号で表現されます。文字と、その文字を表現する Unicode 文字との間に 1 対 1 の関係があると仮定してしまうのは、Unicode 文字列を扱うコードで最もよくある失敗であり、Unicode 文字列の切り詰めを不適切なオフセットで行ってしまうことになります。Unicode 文字列を切り詰めたり、文字の挿入または削除の位置を特定したりするときには、常に Unicode 対応の適切な API を使う必要があります(切り詰めの説明を参照してください)。

(Mac OS X の File Manager で行うように Unicode にエンコードすると、文字列“résumé”には 8 個の UniChar が含まれます。末尾の UniChar を削除すると、“résume”という Unicode 文字列になります)。

 

最後に

32 ビットエンコーディングならば、Unicode は、コードポイント値とエンコードされた値を 1 対 1 で直接対応付けることができ、結果として、文字を正しく挿入したり切り詰めたりできる場所についてのほとんどの問題が解消されます。21 ビットは、約 100 万文字をサポートし、これは現在エンコードされている文字数のほぼ 10 倍に相当します。しかし、コンピュータによって使用され、簡単に処理でき、21 ビットを含む最小のデータ型は、32 ビットです。32 ビットデータ型に基づくエンコーディングスキームでは、無駄な領域が多くなります。もし、Unicode が 32 ビットエンコーディングスキームを採用し、すべてのコードポイント値を単一のコード値でエンコーディングできるようにしたとすると、どの文字も少なくとも 11 ビットを浪費し、よく使われる大部分の文字については、少なくとも 16 ビット/文字を浪費することになるのです。たとえば、32 ビットベースの HFSUniStr255(Mac OS X でファイル名に使用されます)は、ほとんどのファイル名が BMP 中の 40 〜 50 文字以下しか使われていなくても、1,022 バイトを占めることになります。

 

 

参考文献

Laurence Harris, SkyTag Software





先頭に戻る